import * as vscode from "vscode";
import * as path from "path";
import {
    LanguageClient,
    LanguageClientOptions,
    NotificationType,
    RequestType,
    ServerOptions,
    TransportKind
} from "vscode-languageclient/node";
import {
    DsWorkspacePropertiesUpdateParams,
    LanguageCommand,
    OpenDialogOptions,
    QuickPickOptions,
} from "./common/types";
import { getFolderFiles } from "./common/system";
import { ConfigurationManager, createConfigurationManager } from "./configurations";
import { languageClientNotifications, languageClientRequests } from "./util";
import { languageServerNotifications } from "./server/types";

interface LanguageStatus {
    branch: vscode.LanguageStatusItem;
    metadata: vscode.LanguageStatusItem;
}

const statusSelector: vscode.DocumentSelector = [
    { scheme: "file", language: "mrs" },
    { scheme: "file", language: "dms" },
];

export interface DsLanguageClient {
    readonly context: vscode.ExtensionContext;
    readonly configurations: ConfigurationManager;
    showFileBranches(): Promise<void>;
    refreshLanguageStatus(uri: vscode.Uri): Promise<void>;
    selectMetadataFile(): Promise<void>;
    workspaceFoldersChanged(param: vscode.WorkspaceFoldersChangeEvent): void;
    sendRequest<P, R>(type: RequestType<P, R, void>, param: P): Promise<R>;
    sendNotification<P>(type: NotificationType<P>, param: P): Promise<void>;
    start(): Promise<void>;
    stop(): Promise<void>;
}

export function createDsLanguageClient(context: vscode.ExtensionContext): DsLanguageClient {
    const configurations = createConfigurationManager();
    const status: LanguageStatus = {
        branch: vscode.languages.createLanguageStatusItem("ds.branch", statusSelector),
        metadata: vscode.languages.createLanguageStatusItem("ds.metadata", statusSelector),
    };
    status.branch.detail = "入口文件";
    status.metadata.detail = "元数据文件";
    status.branch.command = {
        title: "选择分支",
        command: LanguageCommand.selectFileBranchCommand
    };
    status.metadata.command = {
        title: "选择文件",
        command: LanguageCommand.selectProgramMetadata
    };

    let serverModule = context.asAbsolutePath(path.join("out", "server", "server.js"));
    let debugOption = { execArgv: [ "--nolazy", "--inspect=6009" ] };
    let serverOptions: ServerOptions = {
        run: { module: serverModule, transport: TransportKind.ipc },
        debug: {
            module: serverModule,
            transport: TransportKind.ipc,
            options: debugOption
        }
    };

    let clientOptions: LanguageClientOptions = {
        documentSelector: [
            { scheme: "file", language: "dmrs" },
            { scheme: "file", language: "mrs" },
            { scheme: "file", language: "dms" },
        ],
        synchronize: {
            fileEvents: vscode.workspace.createFileSystemWatcher("**/*.{dms,mrs,ini,inc,bat}")
        }
    };

    const languageClient = new LanguageClient("dsclient", "DS Language Server", serverOptions, clientOptions);

    context.subscriptions.push(languageClient.onRequest(languageClientRequests["dsclient/showOpenFileDialog"], showOpenFileDialog));
    context.subscriptions.push(languageClient.onRequest(languageClientRequests["dsclient/showQuickPickFile"], showQuickPickFile));
    context.subscriptions.push(languageClient.onRequest(languageClientRequests["dsclient/getWindowConfiguration"], () => configurations.getWorkspaceConfiguration()));
    context.subscriptions.push(languageClient.onRequest(languageClientRequests["dsclient/getConfiguration"], getConfiguration));

    context.subscriptions.push(languageClient.onNotification(languageServerNotifications["dsclient/showEditor"], showEditor));
    context.subscriptions.push(languageClient.onNotification(languageClientNotifications["dsclient/closeActivateEditor"], closeEditor));
    context.subscriptions.push(languageClient.onNotification(languageServerNotifications["dsclient/workspaceConfigurationChanged"], updateConfiguration));

    let initializeResult = languageClient.initializeResult;
    if (initializeResult) {
        initializeResult.capabilities.foldingRangeProvider = configurations.getWindowConfiguration().codeFolding;
    }

    return {
        context,
        configurations,
        sendRequest,
        sendNotification,
        showFileBranches,
        refreshLanguageStatus,
        selectMetadataFile,
        workspaceFoldersChanged,
        async start() {
            languageClient.start();
        },
        async stop() {
            languageClient.stop();
            configurations.dispose();
        },
    };

    async function refreshLanguageStatus(uri: vscode.Uri) {
        let dir = path.dirname(uri.fsPath);
        // 刷新入口文件
        let entry = await languageClient.sendRequest(languageClientRequests["dsclient/getFileCurrentBranch"], uri.fsPath);
        status.branch.text = path.relative(dir, entry);
        // 刷新元数据文件
        let metadata = await languageClient.sendRequest(languageClientRequests["dsclient/getProgramMetadata"], uri.fsPath);
        status.metadata.text = metadata ? path.relative(dir, metadata) : "未指定";
    }

    async function showFileBranches() {
        const uri = vscode.window.activeTextEditor?.document.uri;
        if (!uri) {
            return;
        }
        let branches = await languageClient.sendRequest(languageClientRequests["dsclient/getFileBranches"], uri.fsPath);
        if (branches.length > 0) {
            vscode.window.showQuickPick(branches).then(entry => {
                if (entry) {
                    languageClient.sendRequest(
                        languageClientRequests["dsclient/selectFileBranch"],
                        {
                            fsPath: uri.fsPath,
                            selectBranch: entry
                        }
                    ).then(() => refreshLanguageStatus(uri));
                }
            });
        }
    }

    async function selectMetadataFile() {
        let uri = vscode.window.activeTextEditor?.document.uri;
        if (!uri) {
            return;
        }
        showOpenFileDialog({
            defaultPath: path.dirname(uri.fsPath),
            filters: {
                "metadata file": [ "mdd" ]
            },
            canSelectMany: false,
        }).then(result => {
            if (!result || result.length === 0) {
                return;
            }
            languageClient.sendRequest(
                languageClientRequests["dsclient/setProgramMetadata"],
                {
                    uri: uri!.toString(),
                    metadata: result[0]
                }
            );
        }).then(() => {
            refreshLanguageStatus(uri!);
        });
    }

    function getConfiguration(workspaceRoot: string) {
        return configurations.getWorkspaceConfiguration(workspaceRoot);
    }

    function updateConfiguration(param: DsWorkspacePropertiesUpdateParams): void {
        configurations.updateWorkspaceProperties(param.rootPath, "metadata", param.properties.metadata);
        let lineFeedCharater = param.properties["mdmSerializer.lineFeedCharater"];
        if (lineFeedCharater) {
            configurations.updateWorkspaceProperties(param.rootPath, "mdmSerializer.lineFeedCharater", lineFeedCharater);
        }
        let indentSpaceCount = param.properties["mdmSerializer.indentSpaceCount"];
        if (indentSpaceCount !== undefined) {
            configurations.updateWorkspaceProperties(param.rootPath, "mdmSerializer.indentSpaceCount", indentSpaceCount);
        }
    }

    function sendRequest<P, R>(type: RequestType<P, R, void>, param: P): Promise<R> {
        return languageClient.sendRequest(type, param);
    }

    function sendNotification<P>(type: NotificationType<P>, param: P): Promise<void> {
        return languageClient.sendNotification(type, param);
    }

    function workspaceFoldersChanged(param: vscode.WorkspaceFoldersChangeEvent) {
        for (const item of param.removed) {
            configurations.removeWorkspace(item.uri.fsPath);
        }
        for (const item of param.added) {
            configurations.addWorkspace(item.uri.fsPath);
        }
    }

}

function showEditor(fsPath: string) {
    vscode.window.showTextDocument(vscode.Uri.file(fsPath), { preview: false });
}

function closeEditor(fsPath: string) {
    vscode.commands.executeCommand("workbench.action.closeEditor", vscode.Uri.file(fsPath));
}

async function showOpenFileDialog(options?: OpenDialogOptions) {
    let results = await vscode.window.showOpenDialog(options);
    return results ? results.map(e => e.fsPath) : undefined;
}

function showQuickPickFile(options: QuickPickOptions) {
    const extenSet = new Set(options.extens.map(e => e.toLowerCase()));
    const files = options.files ?? getFolderFiles(options.folder, extenSet);
    return vscode.window.showQuickPick(files);
}